#ifndef FLOCK_H
#define FLOCK_H

#include <QHash>
#include <QSet>
#include <QVector>
#include <QVector3D>

#include "geometry/quadtree.h"
#include "geometry/sphere.h"
#include "object/paintable.h"

class QRectF;

class Agent;
class Map;

class Flock : public Paintable
{
public:
    typedef enum
    {
        Constant,
        Linear,
        Quadratic,
        Exponential
    } BlendingFunction;

    typedef enum
    {
        None,
        Attractive,
        Repulsive
    } Disturbance;

    Flock(const Map& rkMap);
    ~Flock();

    // Flock agents & size.
    quint32 count() const;
    QSet< Agent* > agents() const;

    void add(Agent& rkAgent);
    bool remove(Agent& rkAgent);
    void clear();

    // Spatial organization.
    void setBounds(const QRectF& rkBounds = QRectF(-400, -300, 800, 600));
    void setBounds(qreal x, qreal y, qreal width, qreal height);

    Quadtree< Sphere >& bodies() const;
    Quadtree< Sphere >& ranges() const;

    // Flocking.
    qreal repulsionFactor() const;
    qreal alignementFactor() const;
    qreal attractionFactor() const;
    qreal containmentFactor() const;

    qreal containmentRange() const;

    void setRepulsionFactor(qreal factor);
    void setAlignementFactor(qreal factor);
    void setAttractionFactor(qreal factor);
    void setContainmentFactor(qreal factor);

    void setRepulsionFunction(BlendingFunction eType);
    void setAlignementFunction(BlendingFunction eType);
    void setAttractionFunction(BlendingFunction eType);
    void setContainmentFunction(BlendingFunction eType);

    void setContainmentRange(qreal range);

    void setDisturbanceType(Disturbance eType);
    void setDisturbanceFunction(BlendingFunction eType);
    void setDisturbanceFactor(qreal factor);
    void setDisturbancePosition(const QVector3D& rkPosition);
    void setDisturbancePosition(qreal x, qreal y, qreal z);
    void setDisturbanceRange(qreal radius);

    // Update.
    void update();

    // Debug.
    void paint() const;

protected:
    Flock(const Flock&);
    Flock& operator = (const Flock&);

private:
    typedef struct
    {
        QVector< QVector3D > m_kRepulsions;
        QVector< QVector3D > m_kAlignements;
        QVector< QVector3D > m_kAttractions;
    } Accumulator;

    static qreal factor(BlendingFunction eType, qreal proximityRatio);

    void accumulate();
    void blend();
    void contain();
    void disrupt();

    Accumulator& accumulator(Agent& rkAgent);

    //---------------------------------------------------------------
    // Members.
    //---------------------------------------------------------------
    // Map.
    const Map&                   m_rkMap;

    // Agents.
    QSet< Agent* >               m_kAgents;

    // Quadtrees.
    Quadtree< Sphere >*          m_pkRanges;

    // Flock parameters.
    BlendingFunction             m_eRepulsionFunction;
    qreal                        m_repulsionFactor;

    BlendingFunction             m_eAlignementFunction;
    qreal                        m_alignementFactor;

    BlendingFunction             m_eAttractionFunction;
    qreal                        m_attractionFactor;

    BlendingFunction             m_eContainmentFunction;
    qreal                        m_containmentFactor;
    qreal                        m_containmentRange;

    // Disturbance.
    Sphere                       m_kDisturbance;

    Disturbance                  m_eDisturbanceType;
    BlendingFunction             m_eDisturbanceFunction;
    qreal                        m_disturbanceFactor;

    // Accumulators.
    QHash< Agent*, Accumulator > m_kAccumulators;
};

#endif // FLOCK_H
